﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ALProtocol.Tcp
{
    /// <summary>
    /// 表示TCP会话。
    /// </summary>
    public class TcpSession : IDisposable
    {
        /// <summary>
        /// 表示发送参数。
        /// </summary>
        private sealed class SendArgs : SocketAsyncEventArgs
        {
            /// <summary>
            /// 所有者。
            /// </summary>
            private readonly TcpSession m_owner;
            /// <summary>
            /// 实例化一个新的发送参数。
            /// </summary>
            /// <param name="owner"></param>
            public SendArgs(TcpSession owner)
            {
                m_owner = owner;
            }
            /// <summary>
            /// 当发送完成后调用此方法。
            /// </summary>
            /// <param name="e"></param>
            protected override void OnCompleted(SocketAsyncEventArgs e)
            {
                m_owner.OnSendCompleted(e);
            }
        }

        /// <summary>
        /// 同步对象。
        /// </summary>
        private readonly object m_locker = new object();
        /// <summary>
        /// 套接字对象。
        /// </summary>
        private readonly Socket m_socket;
        /// <summary>
        /// 本地地址。
        /// </summary>
        private readonly IPEndPoint m_local;
        /// <summary>
        /// 远程地址。
        /// </summary>
        private readonly IPEndPoint m_remote;
        /// <summary>
        /// 可用标识：1=可用 0=不可用
        /// </summary>
        private int m_available = 1;
        /// <summary>
        /// 数据接收对象。
        /// </summary>
        private TcpReading m_reading = null;
        /// <summary>
        /// 数据发送同步。
        /// </summary>
        private readonly object m_sendLocker = new object();
        /// <summary>
        /// 数据发送状态。
        /// </summary>
        private bool m_sendStatus = false;
        /// <summary>
        /// 数据发送列队。
        /// </summary>
        private readonly Queue<TcpWriting> m_sendQueue = new Queue<TcpWriting>(128);
        /// <summary>
        /// 数据发送参数。
        /// </summary>
        private readonly SendArgs m_sendArgs;

        /// <summary>
        /// 释放通知。
        /// </summary>
        private Action<TcpSession> m_disposed;
        /// <summary>
        /// 释放通知事件。
        /// </summary>
        public event Action<TcpSession> Disposed
        {
            add { m_disposed += value; }
            remove { m_disposed -= value; }
        }
         
        /// <summary>
        /// 实例化一个新的TCP会话。
        /// </summary>
        /// <param name="socket">套接字。</param>
        /// <exception cref="System.ArgumentNullException">当套接字对象为NULL时引发此异常。</exception>
        public TcpSession(Socket socket)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            m_local = socket.LocalEndPoint as IPEndPoint;
            m_remote = socket.RemoteEndPoint as IPEndPoint;
            m_socket = socket;
            m_sendArgs = new SendArgs(this);
        }

         
        /// <summary>
        /// 获取关联的套接字对象。
        /// </summary>
        internal Socket Socket
        {
            get { return m_socket; }
        }
        /// <summary>
        /// 获取本地终结点。
        /// </summary>
        public IPEndPoint Local
        {
            get { return m_local; }
        }
        /// <summary>
        /// 获取远程终结点。
        /// </summary>
        public IPEndPoint Remote
        {
            get { return m_remote; }
        }
        /// <summary>
        /// 获取一个值，该值指示当前是否可用。
        /// </summary>
        public bool Available
        {
            get { return m_available == 1; }
        }

        /// <summary>
        /// 处理读取请求。
        /// 如果会话不可用，则返回False。
        /// </summary>
        /// <param name="reading">TCP数据读取对象。</param>
        /// <exception cref="System.ArgumentNullException">当数据读取对象为NULL时引发此异常。</exception>
        /// <exception cref="System.InvalidOperationException">当数据读取对象正在处理时引发此异步。</exception>
        public bool Process(TcpReading reading)
        {
#if DEBUG
            if (reading == null) throw new ArgumentNullException("reading");
#endif
            if (m_available == 0)
                return false;
            lock (m_locker)
            {
                if (m_available == 0)
                    return false;
                if (m_reading != null)
                    m_reading.Close();
                if (!reading.Start(this))
                    throw new InvalidOperationException();
                m_reading = reading;
                return true;
            }
        }

        /// <summary>
        /// 处理写入请求。
        /// 如果会话不可用，则返回False。
        /// </summary>
        /// <param name="writing"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">当数据写入对象为NULL时引发此异常。</exception>
        /// <exception cref="System.InvalidOperationException">当数据写入对象正在写入数据时引发此异常。</exception>
        public bool Process(TcpWriting writing)
        {
#if DEBUG
            if (writing == null) throw new ArgumentNullException("writing");
#endif
            if (m_available == 0)
                return false;
            if (!writing.Retain(this))
                throw new InvalidOperationException();
            lock (m_locker)
            {
                if (m_available == 0)
                {
                    writing.Fulfil(-1);
                    return false;
                }

                lock (m_sendLocker)
                {
                    if (m_sendStatus)
                    {
                        m_sendQueue.Enqueue(writing);
                        return true;
                    }
                    else
                    {
                        m_sendStatus = true;

                        m_sendArgs.UserToken = writing;
                        m_sendArgs.SetBuffer(writing.Buffer, writing.Offset, writing.Length);
 
                        if (!m_socket.SendAsync(m_sendArgs))
                        {
                            this.OnSendCompleted(m_sendArgs);
                        }

                        return true;
                    }
                }
            }
        }

        /// <summary>
        /// 当发送完成后调用引方法。
        /// </summary>
        /// <param name="e"></param>
        private void OnSendCompleted(SocketAsyncEventArgs e)
        {
            TcpWriting writing = e.UserToken as TcpWriting;
            writing.Fulfil(e.BytesTransferred);
            if (e.SocketError != SocketError.Success)
            {
                Dispose();
            }
            else
            {
                lock (m_locker)
                {
                    lock (m_sendLocker)
                    {
                        if (m_sendQueue.Count > 0)
                        {
                            writing = m_sendQueue.Dequeue();
                            m_sendArgs.UserToken = writing;
                            m_sendArgs.SetBuffer(writing.Buffer, writing.Offset, writing.Length);
                            if (!m_socket.SendAsync(m_sendArgs))
                            {
                                this.OnSendCompleted(e);
                            }
                        }
                        else
                        {
                            m_sendStatus = false;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 当关闭会话后调用此方法。
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        { 
            //add code here
        }
        /// <summary>
        /// 关闭会话，并清理当前对象所占用的相关资源。
        /// </summary>
        public void Dispose()
        {
            lock (m_locker)
            {
                if (Interlocked.CompareExchange(ref m_available, 0, 1) == 1)
                {
                    if (m_reading != null)
                        m_reading.Close();

                    lock (m_sendLocker)
                    {
                        while (m_sendQueue.Count > 0)
                        {
                            TcpWriting writing = m_sendQueue.Dequeue();
                            writing.Fulfil(0);
                        }
                    }
                    m_sendArgs.Dispose();

                    try
                    {
                        m_socket.Shutdown(SocketShutdown.Both);
                    }
                    catch
                    {
                        //add code here
                    }
                    m_socket.Close();
                    m_socket.Dispose();
                    Dispose(true);

                    if (m_disposed != null)
                    {
                        m_disposed(this);
                    }
                }
            }
        }
    }
}